/**
 * @file
 * baiduTemplate简单好用的Javascript模板引擎 1.0.2 版本
 * 开源协议：BSD License
 * 浏览器环境占用命名空间 baidu.template ，nodejs环境直接安装 npm install baiduTemplate
 * @param str{String|HtmlElement} dom结点ID,dom，或者模板string
 * @param data{Object} 需要渲染的json对象，可以为空。当data为{}时，仍然返回html。
 * @return 如果无data，直接返回编译后的函数；如果有data，返回html。
 * @author wangxiao 
 * @email 1988wangxiao@gmail.com
*/


// 模板函数
var bt = function (str, data){

    // 检查是否有该id的元素存在，如果有元素则获取元素的innerHTML/value，否则认为字符串为模板
    var fn = (function () {

        // 判断如果没有document，则为非浏览器环境
        if (!this.document) {
            return compile(str);
        };

        // HTML5规定ID可以由任何不包含空格字符的字符串组成
        var element = document.getElementById(str);
        if (element) {
                
            // 取到对应id的dom，缓存其编译后的HTML模板函数
            if (cache[str]) {
                return cache[str];
            };

            // textarea或input则取value，其它情况取innerHTML
            var html = /^(textarea|input)$/i.test(element.nodeName) ? element.value : element.innerHTML;
            return compile(html);

        }else {

            // 是模板字符串，则生成一个函数
            // 如果直接传入字符串作为模板，则可能变化过多，因此不考虑缓存
            return compile(str);
        };

    })();

    // 有数据则返回HTML字符串，没有数据则返回函数 支持data={}的情况
    return isObject(data) ? fn( data ) : fn;
};

// 取得命名空间 baidu.template
baidu = window.baidu ? window.baidu : {};
baidu.template = bt;

// 缓存  将对应id模板生成的函数缓存下来。
var cache = {};

// 自定义分隔符，可以含有正则中的字符，可以是HTML注释开头 <! !>
// 因为hao123smarty模版的分隔符为'<%'和'%>'，为避免冲突修改默认分隔符为'{%'和'%}' by huangjunwei
bt.LEFT_DELIMITER = bt.LEFT_DELIMITER||'{%';
bt.RIGHT_DELIMITER = bt.RIGHT_DELIMITER||'%}';

// HTML转义
bt._encodeHTML = function (source) {
    return String(source)
        .replace(/&/g,'&amp;')
        .replace(/</g,'&lt;')
        .replace(/>/g,'&gt;')
        .replace(/\\/g,'&#92;')
        .replace(/"/g,'&quot;')
        .replace(/'/g,'&#39;');
};

// 转义影响正则的字符
bt._encodeReg = function (source) {
    return String(source).replace(/([.*+?^=!:${}()|[\]/\\])/g,'\\$1');
};

// 转义UI UI变量使用在HTML页面标签onclick等事件函数参数中
bt._encodeEventHTML = function (source) {
    return String(source)
        .replace(/&/g,'&amp;')
        .replace(/</g,'&lt;')
        .replace(/>/g,'&gt;')
        .replace(/"/g,'&quot;')
        .replace(/'/g,'&#39;')
        .replace(/\\/g,'\\')
        .replace(/\//g,'\/')
        .replace(/\n/g,'\n')
        .replace(/\r/g,'\r');
};

// 将字符串拼接生成函数，即编译过程(compile)
var compile = function (str) {
    return new Function ("_template_object",
        "var _template_fun_array=[];\nwith(_template_object){\n_template_fun_array.push('"+analysisStr(str)+"');\n}\nreturn _template_fun_array.join('');"
    );
};

// 判断是否是Object类型
var isObject = function (source) {
    return 'function' === typeof source || !!(source && 'object' === typeof source);
};

// 解析模板字符串
var analysisStr = function (str) {

    // 取得分隔符
    var _left_ = bt.LEFT_DELIMITER;
    var _right_ = bt.RIGHT_DELIMITER;

    // 对分隔符进行转义，支持正则中的元字符，可以是HTML注释 <!  !>
    var _left = bt._encodeReg(_left_);
    var _right = bt._encodeReg(_right_);

    str=String(str)
        
        // 去掉分隔符中js注释
        .replace(new RegExp("("+_left+"[^"+_right+"]*)// .*\n","g"), "$1")

        // 去掉注释内容  <%* 这里可以任意的注释 *%>
        // 默认支持HTML注释，将HTML注释匹配掉的原因是用户有可能用 <! !>来做分割符
        .replace(new RegExp("<!--.*?-->", "g"),"")
        .replace(new RegExp(_left+"\\*.*?\\*"+_right, "g"),"")

        // 把所有换行去掉  \r回车符 \t制表符 \n换行符
        .replace(new RegExp("[\\r\\t\\n]","g"), "")

        // 用来处理非分隔符内部的内容中含有 斜杠 \ 单引号 ‘ ，处理办法为HTML转义
        .replace(new RegExp(_left+"(?:(?!"+_right+")[\\s\\S])*"+_right+"|((?:(?!"+_left+")[\\s\\S])+)","g"),function (item, $1) {
            var str = '';
            if ($1) {

                // 将 斜杠 单引 HTML转义
                str = $1.replace(/\\/g,"&#92;").replace(/'/g,'&#39;');
                while (/<[^<]*?&#39;[^<]*?>/g.test(str)) {

                    // 将标签内的单引号转义为\r  结合最后一步，替换为\'
                    str = str.replace(/(<[^<]*?)&#39;([^<]*?>)/g,'$1\r$2')
                };
            }else {
                str = item;
            }
            return str ;
        })

        // 对变量后面的分号做容错(包括转义模式 如<%:h=value%>)  <%=value;%> 排除掉函数的情况 <%fun1();%> 排除定义变量情况  <%var val='test';%>
        .replace(new RegExp("("+_left+":?[hvu]?[\\s]*?=[\\s]*?[^;|"+_right+"]*?);[\\s]*?"+_right,"g"),"$1"+_right_)

        // 定义变量，如果没有分号，需要容错  <%var val='test'%>
        .replace(new RegExp("("+_left+"[\\s]*?var[\\s]*?.*?[\\s]*?[^;])[\\s]*?"+_right,"g"),"$1;"+_right_)

        // 按照 <% 分割为一个个数组，再用 \t 和在一起，相当于将 <% 替换为 \t
        // 将模板按照<%分为一段一段的，再在每段的结尾加入 \t,即用 \t 将每个模板片段前面分隔开
        .split(_left_).join("\t")

        // 找到 \t=任意一个字符%> 替换为 ‘，任意字符,'
        // 即替换简单变量  \t=data%> 替换为 ',data,'
        // 默认HTML转义  也支持HTML转义写法<%:h=value%>  
        .replace(new RegExp("\\t(?::h)?=(.*?)"+_right,"g"),"',typeof($1) === 'undefined'?'':baidu.template._encodeHTML($1),'")

        // 支持不转义写法 <%:=value%>和<%-value%>
        .replace(new RegExp("\\t(?::=|-)(.*?)"+_right,"g"),"',typeof($1)==='undefined'?'':$1,'")

        // 支持url转义 <%:u=value%>
        .replace(new RegExp("\\t:u=(.*?)"+_right,"g"),"',typeof($1)==='undefined'?'':encodeURIComponent($1),'")

        // 支持UI 变量使用在HTML页面标签onclick等事件函数参数中  <%:v=value%>
        .replace(new RegExp("\\t:v=(.*?)"+_right,"g"),"',typeof($1)==='undefined'?'':baidu.template._encodeEventHTML($1),'")

        // 将字符串按照 \t 分成为数组，在用'); 将其合并，即替换掉结尾的 \t 为 ');
        // 在if，for等语句前面加上 '); ，形成 ');if  ');for  的形式
        .split("\t").join("');")

        // 将 %> 替换为_template_fun_array.push('
        // 即去掉结尾符，生成函数中的push方法
        // 如：if (list.length=5){%><h2>',list[4],'</h2>');}
        // 会被替换为 if (list.length=5){_template_fun_array.push('<h2>',list[4],'</h2>');}
        .split(_right_).join("_template_fun_array.push('")

        // 将 \r 替换为 \
        .split("\r").join("\\'");
        
    return str;
};

module.exports = bt;
